/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2024-2024. All rights reserved.
 *
 */

import { cloneDeep } from '@baize/wdk';
import { HvigorLogger } from '@ohos/hvigor';
import { glob } from 'glob';
import { minimatch } from 'minimatch';
import path from 'path';

import { CommonConst } from '../const/common-const.js';
import { RestoolCompressionConfig, RestoolCompressionFilterItem } from '../options/build/build-opt.js';
import { toUnixPath } from '../utils/copy-resources-util.js';

import { OptCompressionBuilderInterface } from './restool-builder-interface.js';

const _log = HvigorLogger.getLogger('opt-compression-builder');

const UNIT_TO_SIZE_NUMBER = Object.freeze({
  k: 1024,
  K: 1024,
  m: 1024 * 1024,
  M: 1024 * 1024,
  t: 1024 * 1024 * 1024,
  T: 1024 * 1024 * 1024,
});

interface OptCompressionFilterItem {
  method: {
    type: 'astc' | 'sut';
    blocks: '4x4';
  };
  path?: string[];
  exclude_path?: string[];
  rules_origin?: {
    size?: number[][];
    resolution?: { width: number; height: number }[][];
  };
  rules_exclude?: {
    size?: number[][];
    resolution?: { width: number; height: number }[][];
  };
}

export interface OptCompression {
  context: {
    extensionPath: string;
  };
  compression: {
    media?: {
      enable: boolean;
    };
    sizeLimit?: {
      ratio: number;
    };
    filters: OptCompressionFilterItem[];
  };
}

export class OptCompressionBuilder implements OptCompressionBuilderInterface {
  private optCompressionObj: OptCompression = {
    context: {
      extensionPath: '',
    },
    compression: {
      media: {
        enable: false,
      },
      sizeLimit: undefined,
      filters: [],
    },
  };
  private allResourceFilesAbsolutePath: string[] = [];

  setResourceDirArr(resourceDirArr: string[]): OptCompressionBuilderInterface {
    const resourceDirGlob = resourceDirArr.map((dir) => path.resolve(dir, CommonConst.GLOB_MATCH_ALL_FILES));
    this.allResourceFilesAbsolutePath = glob.sync(resourceDirGlob, {
      nodir: true,
      absolute: true,
      windowsPathsNoEscape: true,
    });
    return this;
  }

  setExtensionPath(extensionPath?: string): OptCompressionBuilderInterface {
    this.optCompressionObj.context.extensionPath = extensionPath ?? '';
    return this;
  }

  setCompressionConfig(compressionConfig: RestoolCompressionConfig | undefined): OptCompressionBuilderInterface {
    this.optCompressionObj.compression.filters = this.convertFilters(compressionConfig?.filters ?? []);

    if (compressionConfig?.media?.enable !== undefined) {
      this.optCompressionObj.compression.media = compressionConfig.media as any;
    }

    if (compressionConfig?.sizeLimit?.ratio !== undefined) {
      this.optCompressionObj.compression.sizeLimit = compressionConfig.sizeLimit as any;
    }

    this.checkMediaFilter();

    return this;
  }

  getOptCompressionObj() {
    return this.optCompressionObj;
  }

  /**
   * 处理区间列表，将所有字符串转换为数字，合并重叠区间
   * [ [0, "1k"], [1024, "2048"] ] --> [ [0, 2048] ]
   * @param size
   * @private
   */
  private processSize(size: Array<string | number>[] | undefined): number[][] | undefined {
    return size
      ?.map((sizeItem) =>
        sizeItem.map((size) => {
          // 将区间中的所有字符串都转换成数字

          if (typeof size === 'number') {
            return size;
          }

          const lastCharIndex = size.length - 1;
          const endChar = size.charAt(lastCharIndex);
          if (Object.prototype.hasOwnProperty.call(UNIT_TO_SIZE_NUMBER, endChar)) {
            const unitNumber = (UNIT_TO_SIZE_NUMBER as any)[endChar];
            return parseInt(size.slice(0, lastCharIndex)) * unitNumber;
          }

          return parseInt(size);
        }),
      )
      .filter(([minSize, maxSize = Number.POSITIVE_INFINITY]) => {
        // 过滤左大右小的区间
        if (minSize > maxSize) {
          _log.warn('Invalid compression filter size will be ignored: ', [minSize, maxSize]);
          return false;
        } else {
          return true;
        }
      })
      .sort((a, b) => a[0] - b[0]) // 根据区间最小值升序排序
      .reduce((mergedResult, curInterval) => {
        // 合并重叠区间
        const lastInterval = mergedResult.length > 0 ? mergedResult[mergedResult.length - 1] : undefined;

        // 前一个区间最大值是无限大，则当前区间可忽略
        if (lastInterval && lastInterval.length === 1) {
          return mergedResult;
        }

        // 区间无重叠，注意 [0,100], [101, 200] 也应该视为区间重叠需要合并为 [0, 200]
        if (!lastInterval || lastInterval[1] < curInterval[0] - 1) {
          mergedResult.push(curInterval);
          return mergedResult;
        }

        // 区间有重叠，且当前区间最大值是无限大，需要修改前一个区间的最大值为无限大
        if (curInterval.length === 1) {
          mergedResult[mergedResult.length - 1] = [lastInterval[0]];
          return mergedResult;
        }

        // 区间有重叠，正常合并重叠区间，如果当前区间的最大值比前一个区间最大值更大则需要修改
        lastInterval[1] = Math.max(lastInterval[1], curInterval[1]);
        return mergedResult;
      }, [] as number[][]);
  }

  /**
   * 校验 resolution 区间，过滤最小宽高大于最大宽高的无效 resolution，如下示例中第一个区间是有效的，第二个是无效的会被过滤掉
   * [
   *   [
   *     {width: 0, height: 0},
   *     {width: 100, height: 100},
   *   ],
   *   [
   *     {width: 100, height: 100},
   *     {width: 0, height: 0},
   *   ]
   * ]
   *
   * @param resolution
   * @private
   */
  private processResolution(resolution: Array<{ width: number; height: number }>[] | undefined): Array<{ width: number; height: number }>[] | undefined {
    return resolution?.filter((item) => {
      const [minResolution, maxResolution = { width: Number.POSITIVE_INFINITY, height: Number.POSITIVE_INFINITY }] = item;

      if (minResolution.width > maxResolution.width || minResolution.height > maxResolution.height) {
        _log.warn('Invalid compression filter resolution will be ignored: ', [...item]);
        return false;
      }
      return true;
    });
  }

  /**
   * 从资源目录文件列表中筛选出与用户填的 path 匹配上的文件
   * @param globArr
   * @private
   */
  private getPathFromResourceFiles(globArr: string[] | undefined) {
    if (!globArr) {
      return undefined;
    }

    return this.allResourceFilesAbsolutePath.filter((resourceFile) => {
      return globArr.some((absoluteGlob) => {
        const absoluteGlobUnixPath = toUnixPath(absoluteGlob); // minimatch 路径分隔符必须是 /
        return minimatch(resourceFile, absoluteGlobUnixPath);
      });
    });
  }

  /**
   * 转换 files 和 exclude 中的 size resolution
   * @param rules
   * @private
   */
  private convertRules(rules: RestoolCompressionFilterItem['files'] | undefined) {
    if (!rules || (!rules.size && !rules.resolution)) {
      return undefined;
    }
    return {
      size: this.processSize(rules.size),
      resolution: this.processResolution(rules.resolution),
    };
  }

  private convertFilters(filterArr: RestoolCompressionFilterItem[]): OptCompressionFilterItem[] {
    return filterArr.map((filter) => {
      const convertedFilter = {
        method: cloneDeep(filter.method),
        path: this.getPathFromResourceFiles(filter.files?.path),
        exclude_path: this.getPathFromResourceFiles(filter.exclude?.path),
        rules_origin: this.convertRules(filter.files),
        rules_exclude: this.convertRules(filter.exclude),
      };

      /**
       * 一般情况下，exclude 的 path, size, resolution 某个属性不填表示该项全排除
       * 假如整个 exclude 不填或者填了 exclude 但其中三个属性都不填，需要做特殊处理，刚好相反不应该全排除
       * 加一个 exclude_path: [] 让 restool 识别为不排除任何东西
       */
      if (!filter.exclude?.path && !filter.exclude?.size && !filter.exclude?.resolution) {
        convertedFilter['exclude_path'] = [];
      }

      return convertedFilter;
    });
  }

  /**
   * 假如 media===true && filters.length===0，需要加上一个默认的 filter
   * @private
   */
  private checkMediaFilter() {
    const isMediaEnable = this.optCompressionObj.compression.media?.enable ?? false;
    const isFiltersEmpty = !this.optCompressionObj.compression.filters?.length;
    if (isMediaEnable && isFiltersEmpty) {
      this.optCompressionObj.compression.filters.push({
        method: {
          type: 'sut',
          blocks: '4x4',
        },
      });
    }
  }
}
